#!/usr/bin/env node
const crypto = require("crypto");
const https = require("https");
const fs = require("fs");

if (process.argv.length < 3) {
    console.error("Usage: node nodeChecksum.js <nodejs_version>");
    process.exit(1);
}

const versions = process.argv[2].split(",");
const architectures = ["amd64", "arm64", "arm", "ppc64le", "s390x"];

const nodeVersions = {};

const calculateChecksum = (url) => {
    return new Promise((resolve, reject) => {
        https
            .get(url, (res) => {
                const hash = crypto.createHash("sha256");
                res.on("data", (data) => {
                    hash.update(data);
                });
                res.on("end", () => {
                    resolve(hash.digest("hex"));
                });
            })
            .on("error", (err) => {
                reject(`Error downloading file: ${err.message}`);
            });
    });
};

const fetchChecksums = async () => {
    for (const nodeVersion of versions) {
		const major = parseInt(nodeVersion.split(".")[0]);
        nodeVersions[nodeVersion] = {};
        await Promise.all(
            architectures.map(async (key) => {
                let arch = key;
				if (major > 22 && key === "arm") {
					return;
				}
                if (key === "amd64") {
                    arch = "x64";
                } else if (key === "arm") {
                    arch = "armv7l";
                }
                const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch}.tar.gz`;
                try {
                    const checksum = await calculateChecksum(url);
                    nodeVersions[nodeVersion][key] = {
                        checksum,
                        suffix: arch,
                    };
                } catch (error) {
                    console.error(error);
                }
            }),
        );
    }
};

fetchChecksums().then(() => {
	let nodeArchives = `"node"

BUILD_TMPL = """\\
# GENERATED BY node_archive.bzl
load("@distroless//private/pkg:debian_spdx.bzl", "debian_spdx")
load("@distroless//private/util:merge_providers.bzl", "merge_providers")
load("@rules_pkg//:pkg.bzl", "pkg_tar")

pkg_tar(
    name = "data",
    srcs = glob(
        [
            "output/bin/node",
            "output/LICENSE",
        ],
    ),
    package_dir = "/nodejs",
    strip_prefix = "external/{canonical_name}/output"
)

pkg_tar(
    name = "_control",
    srcs = ["control"]
)

debian_spdx(
    name = "spdx",
    control = ":_control.tar",
    data = ":data.tar",
    package_name = "{package_name}",
    spdx_id = "{spdx_id}",
    sha256 = "{sha256}",
    urls = [{urls}]
)

merge_providers(
    name = "{name}",
    srcs = [":data", ":spdx"],
    visibility = ["//visibility:public"],
)
"""

def _impl(rctx):
    rctx.report_progress("Fetching {}".format(rctx.attr.package_name))
    rctx.download_and_extract(
        url = rctx.attr.urls,
        sha256 = rctx.attr.sha256,
        type = rctx.attr.type,
        stripPrefix = rctx.attr.strip_prefix,
        output = "output",
    )
    rctx.template(
        "control",
        rctx.attr.control,
        substitutions = {
            "{{VERSION}}": rctx.attr.version,
            "{{ARCHITECTURE}}": rctx.attr.architecture,
            "{{SHA256}}": rctx.attr.sha256,
        },
    )
    rctx.file(
        "BUILD.bazel",
        content = BUILD_TMPL.format(
            canonical_name = rctx.attr.name,
            name = rctx.attr.name.split("~")[-1],
            package_name = rctx.attr.package_name,
            spdx_id = rctx.attr.name,
            urls = ",".join(['"%s"' % url for url in rctx.attr.urls]),
            sha256 = rctx.attr.sha256,
        ),
    )

node_archive = repository_rule(
    implementation = _impl,
    attrs = {
        "urls": attr.string_list(mandatory = True),
        "sha256": attr.string(mandatory = True),
        "type": attr.string(default = ".tar.gz"),
        "strip_prefix": attr.string(),
        "package_name": attr.string(default = "nodejs"),
        "version": attr.string(mandatory = True),
        "architecture": attr.string(mandatory = True),
        # control is only used to populate the sbom, see https://github.com/GoogleContainerTools/distroless/issues/1373
        # for why writing debian control files to the image is incompatible with scanners.
        "control": attr.label(),
    },
)

def _node_impl(module_ctx):
    mod = module_ctx.modules[0]

    if len(module_ctx.modules) > 1:
        fail("node.archive should be called only once")
    if not mod.is_root:
        fail("node.archive should be called from root module only.")

    # Node (https://nodejs.org/en/about/releases/)
    # Follow Node's maintainence schedule and support all LTS versions that are not end of life`;

    for (const nodeVersion of versions) {
		const major = parseInt(nodeVersion.split(".")[0]);

        for (const key of architectures) {
			if (major > 22 && key === "arm") {
				continue;
			}
            const arch = nodeVersions[nodeVersion][key];
            const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch.suffix}.tar.gz`;

			nodeArchives += "\n"
            nodeArchives += `
    node_archive(
        name = "nodejs${major}_${key}",
        sha256 = "${arch.checksum}",
        strip_prefix = "node-v${nodeVersion}-linux-${arch.suffix}/",
        urls = ["${url}"],
        version = "${nodeVersion}",
        architecture = "${key}",
        control = "//nodejs:control",
    )`;
        }

		const testData = `schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node"
    args: ["--version"]
    expectedOutput: ["v${nodeVersion}"]
`

		fs.writeFile(`nodejs/testdata/nodejs${major}.yaml`, testData, (err) => {
			if (err) {
				console.error(err);
			}}
		);
    }
	nodeArchives += `

    return module_ctx.extension_metadata(
        root_module_direct_deps = [`

	for (const nodeVersion of versions) {
		const major = parseInt(nodeVersion.split(".")[0]);
		for (const arch of architectures) {
			if (major > 22 && arch === "arm") {
				continue;
			}
			nodeArchives += `
            "nodejs${major}_${arch}",`
		}
	}

	nodeArchives += `
        ],
        root_module_direct_dev_deps = [],
    )

_archive = tag_class(attrs = {})

node = module_extension(
    implementation = _node_impl,
    tag_classes = {
        "archive": _archive,
    },
)
`

	// Write output to node_archives.bzl file
	fs.writeFile("private/extensions/node.bzl", nodeArchives, (err) => {
		if (err) {
			console.error(err);
		}
	});
});
